package bean;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.ejb.EJB;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;

import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

import bean.remote.ArticuloRemoto;
import bean.remote.ConfiguracionRemote;
import bean.remote.NotificadorLogisticaRemote;
import bean.remote.SolicitudArticuloRemote;
import dominio.Articulo;
import dominio.ArticuloItem;
import dominio.ArticuloItemCompra;
import dominio.Configuracion;
import dominio.SolicitudArticulo;
import dominio.SolicitudCompra;

@Stateless(name = "solicitudArticuloBean")
@Remote(value = SolicitudArticuloRemote.class)
public class SolicitudArticuloBean implements SolicitudArticuloRemote {

	private final static Logger logger = Logger
			.getLogger(SolicitudArticuloBean.class);

	@PersistenceContext(unitName = "webDS")
	private EntityManager em;

	@EJB(name = "articuloBean")
	private ArticuloRemoto articuloRemoto;

	@EJB(name = "configuracionBean")
	private ConfiguracionRemote configuracionRemote;

	@EJB(name = "NotificadorLogisticaBean")
	private NotificadorLogisticaRemote notificadorLogisticaRemote;

	@Override
	public List<SolicitudArticulo> findAll() {
		TypedQuery<SolicitudArticulo> query = em.createQuery(
				"from SolicitudArticulo sa order by sa.fecha asc",
				SolicitudArticulo.class);
		List<SolicitudArticulo> list = query.getResultList();
		logger.info("Se listan todos las solicitudes ");
		return list;
	}

	// ------------------------- Carga desde la cola de mensajeria
	@Override
	public void cargar(SolicitudArticulo solicitudArticulo) {
		if (solicitudArticulo != null) {
			em.persist(solicitudArticulo);
			validarEstado(solicitudArticulo);
			logger.info("Se cargo la solicitud " + solicitudArticulo);
			notificadorLogisticaRemote.notificar("Se cargo la solicitud "
					+ solicitudArticulo);
		} else {
			logger.error("No se cargo la solicitud " + solicitudArticulo);
		}
	}

	@Override
	public SolicitudArticulo findByIdSolicitudAndIdModulo(Long idSolicitud,
			Long idModulo) {
		TypedQuery<SolicitudArticulo> query = em
				.createQuery(
						"from SolicitudArticulo sa where sa.idSolicitud=:idSolicitud and sa.idModulo=:idModulo",
						SolicitudArticulo.class);
		query.setParameter("idSolicitud", idSolicitud).setParameter("idModulo",
				idModulo);
		SolicitudArticulo solicitudArticulo = query.getSingleResult();
		logger.info("Se encontro la siguiente solicitud: " + solicitudArticulo);
		return solicitudArticulo;
	}

	@Override
	public SolicitudArticulo findByCodigo(Long codigo) {
		return em.find(SolicitudArticulo.class, codigo);
	}

	@Override
	public void validarEstado(SolicitudArticulo solicitudArticulo) {
		validarEstado(Collections.singletonList(solicitudArticulo));
	}

	@Override
	public void validarEstado(List<SolicitudArticulo> solicitudArticulos) {

		for (SolicitudArticulo solicitudArticulo : solicitudArticulos) {

			List<ArticuloItem> articulosItems = solicitudArticulo
					.getArticulosItems();

			boolean aFabricar = false;
			Iterator<ArticuloItem> iterator = articulosItems.iterator();

			while (!aFabricar && iterator.hasNext()) {
				ArticuloItem articuloItem = iterator.next();
				Articulo articulo = articuloRemoto.findByCodigo(articuloItem
						.getCodigoArticulo());
				Integer faltanteEntrega = articuloItem.getCantidadSolicitada()
						- articuloItem.getCantidadEntregada();
				if (articulo.getCantidad().compareTo(faltanteEntrega) < 0) {
					aFabricar = true;
				}
			}

			if (aFabricar) {
				solicitudArticulo.aFabricar();
			} else {
				solicitudArticulo.aEntregar();
			}
			em.merge(solicitudArticulo);
		}
	}

	@Override
	public List<SolicitudArticulo> findAllPendientes() {
		TypedQuery<SolicitudArticulo> query = em
				.createQuery(
						"from SolicitudArticulo sa where NOT(sa.estado=3) order by sa.fecha asc",
						SolicitudArticulo.class);
		List<SolicitudArticulo> list = query.getResultList();
		return list;
	}

	// --------------------- Mandar todo a Fabrica
	@Override
	public void crearSolicitudCompra(SolicitudCompra solicitudCompra) {
		em.persist(solicitudCompra);
		logger.info("Se envio a fabricar la siguiente solicitud "
				+ solicitudCompra);

		CloseableHttpClient httpclient = HttpClients.createDefault();

		HttpPost httpPost = new HttpPost(
				"http://localhost:8080/tpIAFabrica/rest/fabrica/procesar");
		try {
			StringEntity input = new StringEntity(solicitudCompra.toJson());
			input.setContentType("application/json");
			httpPost.setEntity(input);

			notificadorLogisticaRemote
					.notificar("Sen envio a procesar a fabrica la siguiente solicitud "
							+ input);

			CloseableHttpResponse execute = httpclient.execute(httpPost);
			HttpEntity entity = execute.getEntity();
			String jsonResult = EntityUtils.toString(entity);
			logger.info("Llego el siguiente mensaje de desde Fabrica "
					+ jsonResult);
			solicitudCompra.enviada();
			em.merge(solicitudCompra);

			notificadorLogisticaRemote
					.notificar("Llego el siguiente mensaje de desde Fabrica "
							+ entity.getContent());

		} catch (ClientProtocolException e1) {
			logger.error("Ocurrio un error en el envio a fabrica", e1);
		} catch (IOException e1) {
			logger.error("Ocurrio un error en el envio a fabrica", e1);
		}
	}

	@Override
	public SolicitudCompra findSolicitudCompraById(Long codigoSolicitudCompra) {
		return em.find(SolicitudCompra.class, codigoSolicitudCompra);
	}

	@Override
	public List<SolicitudCompra> findAllSolicitudesCompra() {
		TypedQuery<SolicitudCompra> query = em.createQuery(
				"from SolicitudCompra sc  order by sc.fecha asc",
				SolicitudCompra.class);
		return query.getResultList();
	}

	@Override
	public void recibirSolicitudCompra(Long codigoSolicitudCompra) {
		SolicitudCompra solicitudCompra = findSolicitudCompraById(codigoSolicitudCompra);
		if (solicitudCompra != null) {
			List<ArticuloItemCompra> articulosItemsCompras = solicitudCompra
					.getArticulosItemsCompra();
			for (ArticuloItemCompra articuloItemsCompras : articulosItemsCompras) {
				Articulo articulo = articuloRemoto
						.findByCodigo(articuloItemsCompras.getCodigoArticulo());
				articuloRemoto.aumentarStock(articulo,
						articuloItemsCompras.getCantidadSolicitada());
			}
			solicitudCompra.recibir();
			em.merge(solicitudCompra);
			logger.info("Se recibio la solicitud de compra " + solicitudCompra);
		}
	}

	// -------------- Entregar a despacho

	@Override
	public void entregar(SolicitudArticulo solicitudArticulo,
			String[] codigosArticulo, String[] cantidadesAEntregar) {

		List<ArticuloDespacho> articulosDespacho = new ArrayList<ArticuloDespacho>();
		Set<ArticuloItem> articuloItemsUpdate = new HashSet<ArticuloItem>(solicitudArticulo.getArticulosItems());
		for (int i = 0; i < codigosArticulo.length; i++) {
			String codigoArticuloString = codigosArticulo[i];
			String cantidadAEntregarString = cantidadesAEntregar[i];

			Integer cantidadAEntregar = cantidadAEntregarString == null
					|| "".equals(cantidadAEntregarString)
					|| " ".equals(cantidadAEntregarString) ? 0 : Integer
					.valueOf(cantidadAEntregarString);

			Long codigoArticulo = Long.valueOf(codigoArticuloString);
			// actualizo el articuloItem
			ArticuloItem articuloItem = solicitudArticulo
					.findArticuloItem(codigoArticulo);
			articuloItem.entregar(cantidadAEntregar);
			articuloItemsUpdate.add(articuloItem);
			// Actualizo el articulo
			Articulo articulo = articuloRemoto.findByCodigo(codigoArticulo);
			articulo.descontarStock(cantidadAEntregar);
			em.merge(articulo);
			// cargo los articulos para despacho
			articulosDespacho.add(new ArticuloDespacho(codigoArticulo,
					cantidadAEntregar));
		}
		//TODO ver que no actualiza el estado de la solicitud porque aun los articulos items esta desactualizados
		solicitudArticulo.entregar(articuloItemsUpdate);
		logger.info("cambio bien el objeto de solicitud de articulos "+ solicitudArticulo);
		em.merge(solicitudArticulo);

		// Mandar a despacho
		JSONObject jsonADespacho = this.toJsonADespacho(solicitudArticulo,
				articulosDespacho);

		CloseableHttpClient httpclient = HttpClients.createDefault();

		Configuracion configuracion = configuracionRemote
				.findByModuleId(solicitudArticulo.getIdModulo());

		String url = "http://" + configuracion.getTerminal() + ":8080"
				+ "/despacho_web/rest/despachador/recibirArticulos";

		HttpPost httpPost = new HttpPost(url);
		try {
			StringEntity input = new StringEntity(jsonADespacho.toJSONString());
			input.setContentType("application/json");
			httpPost.setEntity(input);

			CloseableHttpResponse execute = httpclient.execute(httpPost);
			HttpEntity entity = execute.getEntity();
			String jsonResult = EntityUtils.toString(entity);
			logger.info("Llego el siguiente mensaje de desde Deposito "
					+ jsonResult + " de la configuracion " + configuracion);
		} catch (ClientProtocolException e1) {
			logger.error("Ocurrio un error en el envio a deposito", e1);
		} catch (IOException e1) {
			logger.error("Ocurrio un error en el envio a deposito", e1);
		}

	}

	/**
	 * esta es una clase para poder parsear el json y mandar a despacho
	 * 
	 * @author Maxi.Herrera
	 * 
	 */
	protected class ArticuloDespacho {
		private Long codigo;
		private Integer cantidad;

		public ArticuloDespacho(Long codigo, Integer cantidad) {
			this.codigo = codigo;
			this.cantidad = cantidad;
		}

		public Integer getCantidad() {
			return cantidad;
		}

		public Long getCodigo() {
			return codigo;
		}
	}

	/**
	 * { //codigo de deposito, id de grupo idModulo:xxxx,
	 * 
	 * //codigo solicitud de articulos idSolicitud:xxxx,
	 * 
	 * //Lista de articulos entregados items: [ {codigo:xxxx, cantidad: XXXX},
	 * {codigo:xxxx, cantidad: XXXX} ] }
	 * 
	 * @param solicitudArticulo
	 * @param articuloADespacho
	 */
	@SuppressWarnings("unchecked")
	private JSONObject toJsonADespacho(SolicitudArticulo solicitudArticulo,
			List<ArticuloDespacho> articulosDespacho) {

		JSONObject obj = new JSONObject();
		obj.put("idSolicitud", solicitudArticulo.getIdSolicitud());
		obj.put("idModulo", solicitudArticulo.getIdModulo());
		JSONArray list = new JSONArray();

		for (ArticuloDespacho articuloDespacho : articulosDespacho) {
			JSONObject articuloJson = new JSONObject();
			articuloJson.put("codigo", articuloDespacho.getCodigo());
			articuloJson.put("cantidad", articuloDespacho.getCantidad());
			list.add(articuloJson);
		}
		obj.put("items", list);
		return obj;
	}
}